8장 차원 축소

주요 내용

기본 설정

차원축소 기법 소개

사영 기법

특잇값 분해를 이용한 사영 기법을 설명하기 위해 3차원 공간상에 위치한 60개의 점을 2차원으로 사영하는 예제를 이용한다.

먼저 아래와 같이 3차원 공간상의 60개의 데이터를 (60, 3) 모양의 행렬 X로 생성한다.

X 는 3차원 데이터 60개를 가리킴을 다시 한 번 확인한다.

처음 5개 데이터는 다음과 같다.

특잇값 분해와 PCA

데이터셋의 평균값(mean)이 0이라는 가정하에 PCA를 진행해야 한다.

X데이터 평균값이 0이어야 하는 이유는 PCA를 위해 사용되는 특잇값 분해가 그 조건을 요구하기 때문이다. 즉, 평균이 0인 데이터셋 $X$가 주어졌을 때 아래 조건을 만족시키는 세 개의 행렬 $U$, $\Sigma$, $V$가 존재한다.

$$ X = U\, \Sigma \, V^T $$

참고: 위키백과: 특잇값 분해

특잇값 분해를 진행하는 numpy.linalg.svd() 함수를 이용하여 X_centered에 대해 특잇값 분해를 진행한 결과는 다음과 같다.

각 행렬의 모양을 확인하면 다음과 같다.

앞서 언급한 대로 s는 원래 (60, 3) 모양의 대각 행렬 이어야 하지만 여기서는 대각선 상에 위치한 세 개의 수만 포함하고 있다.

특잇값 분해가 제대로 이루어진 것인지 아래와 같이 검증할 수 있다. 행렬의 곱셈을 진행하려면 먼저 s를 (60, 3) 모양의 대각 행렬로 만들어야 한다.

이제 아래와 같이 특잇값 분해가 제대로 이루어졌음을 확인할 수 있다.

2차원으로 사영하기

주성분 확인

3차원 공간의 데이터를 2차원으로 보내려면 두 개의 주성분(principal component)을 선택해야 한다. 그런데 주성분은 행렬 $V$의 열에 해당하며, 0번 열부터 차례대로 선택하면 된다. 따라서 두 개의 주성분을 아래와 같이 선택한다.

선택된 주성분을 이용하여 X를 2차원 공간으로 사영(projection)시키려면 두 개의 주성분으로 구성된 행렬과 원 데이터 행렬을 곱하면 된다.

주의사항: Vt.T 는 $(V^T)^{T} = V$를 가리킨다.

직접 구한 값과 사이킷런의 PCA 모델을 이용한 값과 비교하기 위해 잠시 기억해둔다.

2차원으로 사영된 데이터의 처음 5개는 다음과 같다.

사이킷런의 PCA 모델 활용

지금까지 살펴본 내용이 사이킷런의 PCA 모델에 구현되어 있다. 또한 평균값을 0으로 맞추는 것도 알아서 해결해준다. 아래 코드가 앞서 진행한 일을 한방에 해결한다.

사이킷런을 이용하여 PCA를 진행할 때 주성분 축이 반대 방향으로 지정되는 경우가 발생한다. 여기서도 그런 일이 벌어졌음을 아래 코드가 알려준다.

재구성 오차

아래 코드는 2차원으로 사영된 데이터를 다시 3차원으로 되돌린다.

2차원 공간으로 사영되면서 정보손실이 있었기에 3차원으로 되돌린 값과 원래의 3차원 값 사이에 오차가 존재한다.

재구성 오차(reconstruction error)는 사영 전과 사영 후 원래 공간으로 되돌린 데이터 사이의 평균제곱오차로 계산한다.

inverse_transform() 메서드가 내부에서는 아래처럼 작동한다.

참고: W2.TVt.T[:2, :] 이다.

앞서 inverse_transform() 메서드의 결과와 비교하기 위해선 다시 데이터셋의 평균값을 빼주어야 한다.

그런데 PCA 모델의 mean_ 속성에 동일한 값이 저장되어 있다.

이제 두 값을 비교하면 다음과 같이 일치한다.

주성분 확인

훈련된 PCA 객체 또한 주성분을 알고 있다.

앞서 확인한 주성분과 비교했을 때 축의 방향만 다름을 확인할 수 있다.

설명 분산 비율

설명 분산 비율 또한 훈련된 객체가 알고 있다.

즉, 2차원으로 사영한 결과 1.1% 정도의 분산을 잃는다.

특잇값 분해로 생성된 행렬 s를 이용하여 설명 분산 비율을 아럐와 같이 계산할 수 있다.

사영 그래프 그리기

3차원 데이터셋을 2차원 데이터셋으로 사영한 결과를 그래프로 확인해 보자. 아래 코드는 3차원 화살표를 그리는 함수를 정의한다.

참조: Plotting a 3d cube, a sphere and a vector in Matplotlib

2차원 데이터셋 그리기

다양체 학습

롤케이크 그리기

참고: 롤케이크를 영어로 스위스 롤(Swiss roll)이라 부른다.

롤케이크 사영하기와 펼치기

다양체 가정

PCA

분산 보존

MNIST 압축하기

주의사항: 사이킷런 0.24 버전부터 fetch_openml()DataFrame 또는 Series 객체를 이용하여 데이터셋을 반환한다. 이를 방지하기 위해 as_frame=False 옵션을 설정한다.

설명 분산 비율이 95%가 되도록 하는 차원을 확인하면 154이다.

설명 분산 비율과 차원과의 관계를 그래프로 그리면 다음과 같다.

차원을 직접 지정하는 대신 설명 분산 비율을 지정하면 자동으로 해당 분산 비율을 만족하는 최소 차원으로 데이터셋을 사영한다.

재구성 오차는 다음과 같다.

원본 손글씨 숫자와 재구성된 숫자 이미지를 비교해보자.

이어서 설명하는 점진전 PCA의 결과와 비교하기 위해 PCA 기법으로 압축된 손글씨 이미지 데이터를 X_reduced_pca 로 기억해두자.

점진적 PCA

Let's compare the results of transforming MNIST using regular PCA and incremental PCA. First, the means are equal:

But the results are not exactly identical. Incremental PCA gives a very good approximate solution, but it's not perfect:

memmap() 함수 사용하기

Let's create the memmap() structure and copy the MNIST data into it. This would typically be done by a first program:

Now deleting the memmap() object will trigger its Python finalizer, which ensures that the data is saved to disk.

Next, another program would load the data and use it for training:

시간 복잡도 비교

Let's time regular PCA against Incremental PCA and Randomized PCA, for various number of principal components:

Now let's compare PCA and Randomized PCA for datasets of different sizes (number of instances):

And now let's compare their performance on datasets of 2,000 instances with various numbers of features:

커널 PCA

그림 8-10 코드

그림 8-11 코드

LLE

기타 차원축소 기법

그림 8-13 코드

연습문제 해법

1. to 8.

See appendix A.

9.

Exercise: Load the MNIST dataset (introduced in chapter 3) and split it into a training set and a test set (take the first 60,000 instances for training, and the remaining 10,000 for testing).

The MNIST dataset was loaded earlier.

Exercise: Train a Random Forest classifier on the dataset and time how long it takes, then evaluate the resulting model on the test set.

Exercise: Next, use PCA to reduce the dataset's dimensionality, with an explained variance ratio of 95%.

Exercise: Train a new Random Forest classifier on the reduced dataset and see how long it takes. Was training much faster?

Oh no! Training is actually more than twice slower now! How can that be? Well, as we saw in this chapter, dimensionality reduction does not always lead to faster training time: it depends on the dataset, the model and the training algorithm. See figure 8-6 (the manifold_decision_boundary_plot* plots above). If you try a softmax classifier instead of a random forest classifier, you will find that training time is reduced by a factor of 3 when using PCA. Actually, we will do this in a second, but first let's check the precision of the new random forest classifier.

Exercise: Next evaluate the classifier on the test set: how does it compare to the previous classifier?

It is common for performance to drop slightly when reducing dimensionality, because we do lose some useful signal in the process. However, the performance drop is rather severe in this case. So PCA really did not help: it slowed down training and reduced performance. :(

Let's see if it helps when using softmax regression:

Okay, so softmax regression takes much longer to train on this dataset than the random forest classifier, plus it performs worse on the test set. But that's not what we are interested in right now, we want to see how much PCA can help softmax regression. Let's train the softmax regression model using the reduced dataset:

Nice! Reducing dimensionality led to over 2× speedup. :) Let's check the model's accuracy:

A very slight drop in performance, which might be a reasonable price to pay for a 2× speedup, depending on the application.

So there you have it: PCA can give you a formidable speedup... but not always!

10.

Exercise: Use t-SNE to reduce the MNIST dataset down to two dimensions and plot the result using Matplotlib. You can use a scatterplot using 10 different colors to represent each image's target class.

The MNIST dataset was loaded above.

Dimensionality reduction on the full 60,000 images takes a very long time, so let's only do this on a random subset of 10,000 images:

Now let's use t-SNE to reduce dimensionality down to 2D so we can plot the dataset:

Now let's use Matplotlib's scatter() function to plot a scatterplot, using a different color for each digit:

Isn't this just beautiful? :) This plot tells us which numbers are easily distinguishable from the others (e.g., 0s, 6s, and most 8s are rather well separated clusters), and it also tells us which numbers are often hard to distinguish (e.g., 4s and 9s, 5s and 3s, and so on).

Let's focus on digits 3 and 5, which seem to overlap a lot.

Let's see if we can produce a nicer image by running t-SNE on these 3 digits:

Much better, now the clusters have far less overlap. But some 3s are all over the place. Plus, there are two distinct clusters of 2s, and also two distinct clusters of 5s. It would be nice if we could visualize a few digits from each cluster, to understand why this is the case. Let's do that now.

Exercise: Alternatively, you can write colored digits at the location of each instance, or even plot scaled-down versions of the digit images themselves (if you plot all digits, the visualization will be too cluttered, so you should either draw a random sample or plot an instance only if no other instance has already been plotted at a close distance). You should get a nice visualization with well-separated clusters of digits.

Let's create a plot_digits() function that will draw a scatterplot (similar to the above scatterplots) plus write colored digits, with a minimum distance guaranteed between these digits. If the digit images are provided, they are plotted instead. This implementation was inspired from one of Scikit-Learn's excellent examples (plot_lle_digits, based on a different digit dataset).

Let's try it! First let's just write colored digits:

Well that's okay, but not that beautiful. Let's try with the digit images:

Exercise: Try using other dimensionality reduction algorithms such as PCA, LLE, or MDS and compare the resulting visualizations.

Let's start with PCA. We will also time how long it takes:

Wow, PCA is blazingly fast! But although we do see a few clusters, there's way too much overlap. Let's try LLE:

That took a while, and the result does not look too good. Let's see what happens if we apply PCA first, preserving 95% of the variance:

The result is more or less the same, but this time it was almost 4× faster.

Let's try MDS. It's much too long if we run it on 10,000 instances, so let's just try 2,000 for now:

Meh. This does not look great, all clusters overlap too much. Let's try with PCA first, perhaps it will be faster?

Same result, and no speedup: PCA did not help (or hurt).

Let's try LDA:

This one is very fast, and it looks nice at first, until you realize that several clusters overlap severely.

Well, it's pretty clear that t-SNE won this little competition, wouldn't you agree? We did not time it, so let's do that now:

It's twice slower than LLE, but still much faster than MDS, and the result looks great. Let's see if a bit of PCA can speed it up:

Yes, PCA roughly gave us over 2x speedup, without damaging the result. We have a winner!